package summarizer;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.PriorityQueue;

import summarizer.newversion.Summarizer;
import thesis.FSModule;
import thesis.InfoUnit;
import thesis.DataObject;
import thesis.Summary;
import thesis.DataUtil;


public class GreedySummarizerIterationOpt extends Summarizer {

	private final double alpha;
	private final double beta;
	private final double gamma;
	private final double[][] probs;
	private final ArrayList<InfoUnit> infoUnits;
	private HashMap<Long, DataObject> memoryTweets;
	private double totalSize;
	private double totalInfo;

	public GreedySummarizerIterationOpt(double[][] probs,
			ArrayList<InfoUnit> infoUnits, double alpha, double beta,
			double gamma) {
		this.alpha = alpha;
		this.beta = beta;
		this.gamma = gamma;

		this.probs = probs;
		this.infoUnits = infoUnits;
	}

	private DataObject preprocess(Summary summary, int summaryDimension,
			int dimSize) {
		// initialized totalInfo contained in each tweet
		this.totalInfo = DataUtil.getInfoWeight(dimSize);
		for (DataObject t : this.memoryTweets.values()) {
			double total = 0;
			for (InfoUnit unit : this.infoUnits) {
				total += probs[t.getInternId()][unit.getInternId()]
						* unit.getWeight();
			}
			t.setQual(t.getQuality() / summaryDimension);
			t.setDiv(1);
		}
		for (InfoUnit unit : this.infoUnits) {
			double gain = 0;
			for (DataObject t : memoryTweets.values()) {
				gain += unit.getWeight()
						* probs[t.getInternId()][unit.getInternId()]
						/ this.totalInfo;
			}
			unit.setGain(gain);
		}
		for (DataObject t : this.memoryTweets.values()) {
			double cov = 1;
			for (InfoUnit unit : this.infoUnits) {
				double covProb = probs[t.getInternId()][unit.getInternId()];
				double gainT = this.totalInfo == 0 ? 0 : (covProb
						* unit.getWeight() / this.totalInfo);
				cov += covProb * (unit.getGain() - gainT);
			}
			t.setOrigCov(cov / totalSize);
		}
		// find tweet with best quality
		DataObject bestT = null;
		double bestScore = -1;
		for (DataObject t : memoryTweets.values()) {
			double score = alpha * t.getQual() + gamma * t.getOrigCov();
			if (score > bestScore) {
				bestScore = score;
				bestT = t;
			}
		}
		summary.addMemoryTweet(bestT);
		this.memoryTweets.remove(bestT.getDbId());
		for (InfoUnit unit : this.infoUnits) {
			unit.setCovProb(probs[bestT.getInternId()][unit.getInternId()]);
			unit.setGain(unit.getGain()
					- probs[bestT.getInternId()][unit.getInternId()]
					* unit.getWeight() / this.totalInfo);
		}
		return bestT;
	}

	private void greedProcess(int summaryDimension, int numberOfPages,
			Summary summary, DataObject bestT) {
		double totalOps = 0, actualOps = 0;
		double totalQuality = bestT.getQuality() / summaryDimension;
		PriorityQueue<DataObject> pq = new PriorityQueue<DataObject>(
				this.memoryTweets.size(), new Comparator<DataObject>() {
					@Override
					public int compare(DataObject o1, DataObject o2) {
						if (o1.getUp() > o2.getUp())
							return -1;
						else if (o1.getUp() == o2.getUp())
							return 0;
						else
							return 1;
					}
				});
		List<DataObject> activeList = new ArrayList<DataObject>(this.memoryTweets.size());
		for (DataObject t : this.memoryTweets.values()) {
			activeList.add(t);
		}
		// calculate coverage for bestT
		double covBestT = 1;
		for (InfoUnit unit : this.infoUnits) {
			covBestT += unit.getCovProb() * unit.getGain();
		}
		bestT.setCov(covBestT / this.totalSize);
		while (summary.size() < summaryDimension) {
			double prevDiv = bestT.getDiv(), prevCov = bestT.getCov();
			for (DataObject t : activeList) {
				if (t != bestT) {
					double upper = alpha
							* t.getQual()
							+ beta
							* Math.min(Math.min(t.getDiv(), prevDiv),
									DataUtil.dist(t, bestT)) + gamma
							* Math.min(1 - prevCov, t.getOrigCov());
					t.setUp(upper);
					pq.add(t);
				}
			}
			double bestScore = -1;
			activeList.clear();
			while (!pq.isEmpty()) {
				DataObject t = pq.poll();
				// compute div_t
//				double minDist = Double.MAX_VALUE;
//				for (MemoryTweet tInS : summary.getMemoryTweets()) {
//					double dist = FSModule.dist(t, tInS);
//					if (dist < minDist) {
//						minDist = dist;
//					}
//				}
//				t.setDiv(Math.min(prevDiv, minDist));
				// compute cov_t
				double cov = summary.size() + 1;
				for (InfoUnit unit : this.infoUnits) {
					double covProb = probs[t.getInternId()][unit.getInternId()];
					double gainT = this.totalInfo == 0 ? 0 : (covProb
							* unit.getWeight() / this.totalInfo);
					cov += (1 - (1 - unit.getCovProb()) * (1 - covProb))
							* (unit.getGain() - gainT);
				}
				t.setCov(cov / totalSize);
				double score = alpha * t.getQual() + beta * t.getDiv() + gamma
						* (t.getCov() - prevCov);
				if (score > bestScore) {
					bestScore = score;
					bestT = t;
				}
				activeList.add(t);
				if (bestScore > t.getUp()) {
					break;
				}
			}
			totalOps += activeList.size() + pq.size();
			actualOps += activeList.size();
			summary.addMemoryTweet(bestT);
			this.memoryTweets.remove(bestT.getDbId());
			// compute coverage of each information unit by current summary
			for (InfoUnit info : this.infoUnits) {
				info.setCovProb(1 - (1 - info.getCovProb())
						* (1 - probs[bestT.getInternId()][info.getInternId()]));
				double gainBest = this.totalInfo == 0 ? 0
						: (probs[bestT.getInternId()][info.getInternId()]
								* info.getWeight() / this.totalInfo);
				info.setGain(info.getGain() - gainBest);
			}
			totalQuality += bestT.getQual();
			if (FSModule.DEBUG) {
				System.out.println("Summary size: " + summary.size());
				System.out.println("op ratio: " + actualOps / totalOps);
				System.out.println("Quality: " + totalQuality + " Diversity: "
						+ bestT.getDiv() + " Coverage: " + bestT.getCov());
			}
		}
		//System.out.println("Operation ratio: " + actualOps / totalOps);
	}

	public Summary computeSummary(int summaryDimension, int numberOfPages,
			HashMap<Long, DataObject> tweets, int dimSize) {
		this.memoryTweets = tweets;
		this.totalSize = this.memoryTweets.size();
		Summary summary = new Summary();
		if (summaryDimension <= 0 || this.memoryTweets.isEmpty()) {
			return summary;
		}
		executionTime = (new GregorianCalendar()).getTimeInMillis();
		DataObject bestT = preprocess(summary, summaryDimension, dimSize);
		greedProcess(summaryDimension, numberOfPages, summary, bestT);
		executionTime = (new GregorianCalendar()).getTimeInMillis()
				- executionTime;
		return summary;
	}

	public String toString() {
		return "Greedy Summarizer Iteration Opt <" + alpha + " " + beta + " "
				+ gamma + ">";
	}

	public long getExecutionTime() {
		return executionTime;
	}
}
